home *** CD-ROM | disk | FTP | other *** search
/ FishMarket 1.0 / FishMarket v1.0.iso / fishies / 076-100 / disk_094 / audiotools / audiotools2.c < prev    next >
C/C++ Source or Header  |  1992-05-06  |  33KB  |  1,082 lines

  1. /* audiotools.h built in here so that audiotools is a package deal */
  2.  
  3. #define StartChannel(c) ControlChannel(c, CMD_START)
  4. #define StopChannel(c)  ControlChannel(c, CMD_STOP)
  5. #define ResetChannel(c) ControlChannel(c, CMD_RESET)
  6. #define FlushChannel(c) ControlChannel(c, CMD_FLUSH)
  7.  
  8. #define  BIG_WAVE          256L   /* size of biggest waveform */
  9. #define  NBR_WAVES         7L     /* number of waves per instrument */
  10. #define  WAVES_TOTAL       1024L  /* alloc size for instrument's waves */
  11.  
  12. #define  DEFAULT_DURATION  500L      /* 500/1000ths of a second default */
  13. #define  AUDBUFFERS        20L      /* iob msg packets before need to allot */
  14. #define  YES               1L
  15. #define  NO                0L
  16.  
  17. #define  BAD_CHANNEL_SELECTED -1L /* channel # out of range */
  18. #define  NOT_YOUR_CHANNEL     -2L /* not owned by your task */
  19. #define  OUT_OF_RANGE_FREQ    -3L /* frequency that we cannot play */
  20. #define     OUT_OF_RANGE_PRI     -4L /* priority value wont fit in 1 byte */
  21.  
  22. /* REDEFINITION from article - now contains one new field at the bottom
  23.  * of the data structure.
  24.  */
  25. struct ExtIOB {
  26.     struct     IORequest ioa_Request;
  27.     WORD    ioa_AllocKey;
  28.     UBYTE    *ioa_Data;
  29.     ULONG    ioa_Length;
  30.     UWORD    ioa_Period;
  31.     UWORD    ioa_Volume;
  32.     UWORD    ioa_Cycles;
  33.     struct    Message ioa_WriteMsg;    /* up to here, same as IOAudio */
  34.     LONG    iob_Identifier;        /* This field is added */
  35. };
  36.  
  37. /* a few forward declarations */
  38.  
  39. extern struct ExtIOB     *GetIOB();
  40. extern int         FreeIOB();
  41. extern int         GetChannel();
  42. extern int         GetStereoPair();
  43. extern int         InitBlock();
  44. extern struct MsgPort     *CreatePort();
  45.  
  46. extern APTR         AllocMem();
  47. extern struct Message     *GetMsg();
  48. extern struct Task    *FindTask();
  49.  
  50. struct auMsg {
  51.    struct Message au_Message;
  52.    LONG aum_Identifier;   /* matches the bottom of ExtIOB */
  53. };
  54.  
  55. /* Note that the definition of ExtIOB has now been changed (added to) */
  56.  
  57. /* forward declaration */
  58. extern struct MsgPort *InitAudio();
  59.  
  60. /* these are used to keep track of allocated channels */
  61.  
  62. struct Unit    *unit[4];    /* global pointers to Units        */
  63. WORD         key[4];        /* global value for alloc keys     */
  64. struct Task     *usertask[4];    /* in preparation for making this
  65.                  * a shared library of routines
  66.                  * (loadable from disk), keep track
  67.                  * of which user actually owns a
  68.                  * channel currently.
  69.                  */
  70.  
  71. struct  IOAudio     openIOB;      /* IOB to open and close the device */
  72. struct  MsgPort     *auReplyPort; /* temporary pointer */
  73. struct  MsgPort     *controlPort; /* Port for ControlChannel functions */
  74.  
  75. char    *globalname     = "global";    /* the name for global IOB's  */
  76. char    *dynamicname     = "dynamic";    /* the name for dynamic IOB's */
  77.  
  78. UBYTE stereostuff[4]     = { 3, 5, 10, 12 };    /* channel masks for stereo */
  79. UBYTE anychan[4]     = { 1, 2, 4, 8 };    /* channel masks for mono */
  80.  
  81. /* Resolve most all externals */
  82.  
  83. struct ExtIOB          audbuffer[AUDBUFFERS];    /* globals to build-in       */
  84. UBYTE             *chipaudio[4];    /* pointers to waveforms in CHIP RAM */
  85. struct Device         *device;    /* global pointer to audio device  */
  86. LONG             datalength[4];    /* length of the data for a wave   */
  87. struct MsgPort         *replyPort[4];  /* one ReplyPort per channel       */
  88. BYTE             inuse[AUDBUFFERS]; /* keep track of globals in-use */
  89. LONG            dynamix[4];    /* counters for how many
  90.                      * dynamically allocated audio
  91.                      * message I/O blocks 
  92.                      */
  93.  
  94.  
  95. /* Each waveform buffer contains 8 octaves of the wave.  
  96.  * The offset values specify where in the buffer the
  97.  * proper waveform table for that octave begins.
  98.  */
  99. int woffsets[]     = { 0, 256, 384, 448, 480, 496, 504, 508, 510 };
  100.  
  101. /* Length of each waveform within a buffer */
  102. int wlen[]     = { 256, 128, 64, 32, 16, 8, 4, 2, 1 };
  103.  
  104. /* Period value to go with particular notes within an octave. */
  105. int perval[]     = { 428, 404, 381, 360, 339, 320, 
  106.                  302, 285, 269, 254, 240, 226, 214 };
  107.  
  108. UBYTE *w1, *w2, *w3;
  109. BYTE *owptr[4]     = { NULL, NULL, NULL, NULL };
  110.  
  111. /* InitAudio is different from that published in the article -
  112.  
  113. InitAudio now returns a value, an address of a message port at which
  114. your task receives a message when a particular note BEGINS to play.
  115. You must save this value somewhere, and use it to call MayGetNote
  116. or FinishAudio.  MayGetNote is the name of the routine that you call
  117. to check if a note has begun to play.
  118.  
  119. InitAudio also has been modified to return to its caller when there
  120. are problems, rather than calling finishup routines itself.  This is better
  121. programming practice.
  122.  
  123. */
  124.  
  125. struct MsgPort *    
  126. InitAudio()
  127. {
  128.       LONG error,i;
  129.       struct MsgPort *userport;    /* NEW item */
  130.  
  131.       LONG firstuser;    /* THIS WILL GET MOVED when shared library is made */
  132.       firstuser = TRUE;
  133.  
  134.       /* Declare all message blocks available */
  135.       for(i=0; i<AUDBUFFERS; i++)  {   inuse[i] = NO;   }
  136.  
  137.       /* Open device but don't allocate channels     */
  138.       openIOB.ioa_Length = 0;   /* (no allocation table) */
  139.  
  140.       error = OpenDevice("audio.device",0,&openIOB,0);
  141.       if(error) return((struct MsgPort *)0);
  142.  
  143.       /* Get the device address for later use */
  144.       device = openIOB.ioa_Request.io_Device;
  145.    
  146.    /* Create ports for replies from each channel as well as
  147.     * one port to be used for the control and synchonous functions */
  148.  
  149.    for(i=0; i<4; i++) 
  150.    {   
  151.       auReplyPort = CreatePort(0,0);
  152.       replyPort[i] = auReplyPort;
  153.       if(auReplyPort == 0) return((struct MsgPort *)0);
  154.       chipaudio[i] = 0;  /* have not yet created the waves */
  155.  
  156.       datalength[i] = 1; /* might use for custom sound samples  */
  157.  
  158.       /* Also, zero out key values for each channel, as well as
  159.        * unit value and usertask value (no channel owned by any task)
  160.        */
  161.  
  162.     /* When implemented as a shared library, "firstuser" will only 
  163.       * be true when the library is first opened.
  164.       */
  165.       if(firstuser)    
  166.       {
  167.     key[i]  = 0;
  168.     unit[i] = 0;
  169.     usertask[i] = 0;
  170.       }
  171.    }
  172.    controlPort = CreatePort(0,0);
  173.    if(controlPort == 0) return((struct MsgPort *)0);
  174.  
  175.    error = MakeWaves();
  176.    if(error == -1) return((struct MsgPort *)0);
  177.  
  178.    for(i=0; i<4; i++)
  179.    { dynamix[i] = 0; }   /* no dynamic I/O blocks allocated 
  180.                       * for any channel thus far */
  181.  
  182.    userport = CreatePort(0,0);
  183.  
  184. return(userport);
  185. }
  186.  
  187. int 
  188. CheckIOBDone()
  189. {
  190.    LONG i, status;
  191.  
  192.    status = 0;   /* means there are still some iob's in play */
  193.          /* when status = 5, then everything is free */
  194.  
  195.    for(i=0; i<AUDBUFFERS; i++)
  196.    {   if(inuse[i] == YES)
  197.        {   
  198.      /* Sooner or later, this will catch both
  199.           * the statics and dynamics.  Note that
  200.           * this will only work if NO (REPEAT: NO)
  201.           * iob's sent off with a duration value
  202.           * of "0", because zero means "forever"
  203.           */
  204.          ReEmployIOB();
  205.       }
  206.    }
  207.    /* Note to implementors... maintaining inuse[i] now seems
  208.     * like a lousy idea, unless it is accompanied by a variable
  209.     * statics_inplay that decrements to zero when all statics
  210.     * are done.  That makes it much easier to check than going
  211.     * through all of the inuse[]'s.
  212.     */
  213.  
  214.    for(i=0; i<4; i++)
  215.    {   
  216.       if(dynamix[i] > 0)  
  217.             /* If this channel still playing a   */
  218.                  /* dynamically allocated block, wait */
  219.                  /* for all messages to return before */
  220.                  /* the program exits.                */
  221.       {
  222.              ReEmployIOB();  /* take another shot at freeing it all */  
  223.       }
  224.    }
  225.    for(i=0; i<4; i++)   /* Check again as we nearly exit */
  226.    {
  227.       if(dynamix[i] == 0) status++;
  228.    }
  229.    if(status == 4)      /* All dynamics are free, now check
  230.              * the statics.  Any not free force
  231.              * an early return. */
  232.    {
  233. #ifdef DEBUG
  234.       printf("ch0,1,2,3 have %ld,%ld,%ld,%ld dyn.blocks playing\n",
  235.          dynamix[0], dynamix[1], dynamix[2], dynamix[3]);
  236. #endif DEBUG
  237.       for(i=0; i<AUDBUFFERS; i++)
  238.       {
  239.          if(inuse[i] == YES)
  240.          {
  241. #ifdef DEBUG
  242.          printf("iob still in use is: %ld\n",i);
  243. #endif DEBUG
  244.          return(0);
  245.          }
  246.       }
  247.       printf("All global I/O blocks are done\n");
  248.  
  249.       return(1);   /* DONE! */
  250.    }
  251.    else
  252.    {
  253.       return(0);   /* still some out there! */
  254.    }
  255. }
  256.  
  257.  
  258. FinishAudio(uport)
  259. struct MsgPort *uport;
  260. {
  261.    LONG i;
  262.    struct auMsg *aum;        /* A little bigger than a standard
  263.                      * message, but this routine will
  264.                      * not really know (or care) about
  265.                      * the difference.
  266.                      */
  267.    if(uport == 0)
  268.    {
  269.     goto no_init;        /* InitAudio didn't work, bypass
  270.                  * the usual port emptying and so on.
  271.                  */
  272.    }
  273.    /* If the user says FinishAudio, IT MEANS FINISH AUDIO.
  274.     * Flush anything that is still in play, NOW.  You can
  275.     * use "CheckIOBDone()" to see if everything is finished
  276.     * BEFORE you call FinishAudio.  If CheckIOBDone() is
  277.     * equal to zero (FALSE), it means that something is still
  278.     * playing.
  279.     */
  280.    for(i=0; i<4; i++)   FlushChannel(i);
  281.  
  282.    while(CheckIOBDone() == 0)
  283.    {
  284.       Delay(12);   /* Be a good multitasking neighbor;
  285.             * sleep a little before trying again */
  286.    }
  287.    /* Empty the port if the user has not yet done so */
  288.    while((aum = (struct auMsg *)GetMsg(uport)) != NULL)
  289.    {
  290.       aum->au_Message.mn_ReplyPort = 0;   /* let system deallocate it */
  291.    }
  292.    ReEmployIOB();   /* free all static and dynamic messages */
  293.  
  294.    for(i=0; i<4; i++)   FreeChannel(i);
  295.  
  296. no_init:
  297.    if(device) CloseDevice(&openIOB);
  298.    printf("closed the device\n");
  299.  
  300.    for(i=0; i<4; i++)
  301.    {
  302.     if(chipaudio[i]) FreeMem(chipaudio[i],WAVES_TOTAL);
  303.     if(replyPort[i]) 
  304.     DeletePort(replyPort[i]);
  305.    }
  306.    if(controlPort) DeletePort(controlPort);
  307.  
  308.    return(0);      /* no errors */
  309. }
  310.  
  311.  
  312. int
  313. ControlChannel(channel, command)
  314.    WORD channel;
  315.    WORD command;
  316. {
  317.    LONG rtn;
  318.    struct ExtIOB *iob, controlIOB;
  319.  
  320.    if(channel < 0 || channel > 3)    return(BAD_CHANNEL_SELECTED);
  321.    if(usertask[channel] != FindTask(0))    return(NOT_YOUR_CHANNEL);
  322.  
  323.    iob = &controlIOB;
  324.    iob->ioa_Request.io_Device    = device;
  325.    iob->ioa_Request.io_Message.mn_ReplyPort = controlPort;
  326.  
  327.    InitBlock(iob,channel);   /* init it for CMD_WRITE, then change */
  328.  
  329.    iob->ioa_Request.io_Command = command;
  330.    iob->ioa_Request.io_Flags   = IOF_QUICK;
  331.  
  332.    BeginIO(iob);
  333.    WaitIO(iob);
  334.    rtn = ((LONG)(iob->ioa_Request.io_Error));
  335.    return(rtn);
  336. }
  337.  
  338. struct ExtIOB *
  339. GetIOB(ch)
  340.    LONG ch;
  341. {
  342.    WORD i,use_reply;
  343.    struct ExtIOB *iob;  /* in case we need to allocate one */
  344.    ReEmployIOB();    /* find already used ones and free them */
  345.               /* so that when we do a get... */
  346.    if(ch == -1)  use_reply = 0;  /* which reply port to use */
  347.    else          use_reply = ch;
  348.  
  349.    for(i=0; i<AUDBUFFERS; i++)
  350.    {   
  351.     if(inuse[i] == NO)
  352.            {   
  353.         inuse[i] = YES;
  354.  
  355.         audbuffer[i].ioa_Request.io_Device    = device;
  356.         audbuffer[i].ioa_Request.io_Message.mn_ReplyPort = 
  357.                                   replyPort[use_reply];
  358.         audbuffer[i].ioa_Request.io_Message.mn_Length = i;
  359.         audbuffer[i].ioa_Request.io_Message.mn_Node.ln_Name = 
  360.                                   globalname;
  361. #ifdef DEBUG
  362.       printf("Using global iob\n");
  363. #endif DEBUG
  364.       return(&audbuffer[i]);
  365.        }
  366.    }
  367.    /* if all globals are in use, have to allocate one */
  368.    iob = (struct ExtIOB *)AllocMem(sizeof(struct ExtIOB),
  369.                      MEMF_CLEAR | MEMF_FAST );
  370.    if(iob == 0) return(0);   /* out of memory */
  371.    else
  372.    {   
  373.     iob->ioa_Request.io_Device = device;
  374.           iob->ioa_Request.io_Message.mn_ReplyPort = 
  375.                replyPort[use_reply];
  376.           iob->ioa_Request.io_Message.mn_Node.ln_Name = 
  377.                dynamicname;
  378.           iob->ioa_Request.io_Message.mn_Length = dynamix[use_reply];
  379.           dynamix[use_reply] += 1; /* add one to number allocated
  380.                           * for a specific channel */
  381. #ifdef DEBUG
  382.           printf("Using dynamic iob\n");
  383. #endif DEBUG
  384.           return(iob);
  385.     }
  386. return(0);
  387. }
  388.  
  389.  
  390. /* Free a global or an allocated IOB */
  391. int
  392. FreeIOB(iob, ch)
  393.    struct ExtIOB *iob;
  394.    LONG ch;   /* which channel was it attached to? */
  395. {
  396.    WORD i;
  397.  
  398.    if(iob->ioa_Request.io_Message.mn_Node.ln_Name == dynamicname)
  399.    {   
  400.     FreeMem(iob, sizeof(struct ExtIOB));
  401.           if(dynamix[ch]) dynamix[ch] -= 1; /* subtract one if nonzero */
  402.           return(0L);
  403.    }
  404.    else if(iob->ioa_Request.io_Message.mn_Node.ln_Name == globalname)
  405.    {       
  406.     i = iob->ioa_Request.io_Message.mn_Length;
  407.  
  408.           if(i < AUDBUFFERS)
  409.           {   
  410.         inuse[i] = NO;   /* frees this one for reuse */
  411.           }
  412.           return(0L);
  413.    }
  414.    /* if get here, the names don't match... something is wrong.*/
  415.    else 
  416.    {   
  417.         printf("FreeIOB: names don't match...unknown error\n");
  418.            return(-1);   /* unknown source of IOB fed to routine. */
  419.    }
  420. return(0);
  421. }
  422.  
  423. /* Initialize an audio I/O block for default CMD_WRITE operation. */
  424. int
  425. InitBlock(iob, channel)
  426.      struct ExtIOB *iob;
  427.    WORD channel;
  428. {
  429.    /* Device and ReplyPort fields have been initialized by GetIOB */
  430.    iob->ioa_Request.io_Unit = unit[channel];
  431.  
  432.    /* Allocation key */
  433.    iob->ioa_AllocKey = key[channel];
  434.  
  435.    /* Where is the waveform?  Just be sure is in MEMF_CHIP!!! */
  436.  
  437.    /* USER initializes datalength[ch] before calling this;    */
  438.    /* for sampled sound command write operation.              */
  439.    iob->ioa_Data    = chipaudio[channel];
  440.    iob->ioa_Length = datalength[channel];
  441.  
  442.    /* Another routine, must initialize:
  443.  
  444.       period      ioa_Period
  445.       volume      ioa_Volume
  446.       cycles      ioa_Cycles
  447.       message      ioa_WriteMsg
  448.    */
  449.    /* Default command type is CMD_WRITE */
  450.    iob->ioa_Request.io_Command = CMD_WRITE;
  451.  
  452.    /* If IOF_QUICK is zeroed, this would affect the
  453.     * period and volume.  If a CMD_WRITE, it queues if
  454.     * another note is already playing.  We queue CMD_WRITES.
  455.     */
  456.    iob->ioa_Request.io_Flags = ADIOF_PERVOL;
  457.    return(0);
  458. }
  459.  
  460.  
  461. /* To request "any" channel, use ch = -1;
  462.  * To request a specific channel, use ch = {0, 1, 2 or 3};
  463.  * Again NOTE, this returns two globals as well as the channel number!
  464.  */
  465.  
  466. int
  467. GetChannel(ch)
  468.    LONG ch;
  469. {
  470.    int error, value;
  471.    struct ExtIOB *iob, controlIOB;
  472.  
  473.    iob = &controlIOB;
  474.    iob->ioa_Request.io_Device    = device;
  475.    iob->ioa_Request.io_Message.mn_ReplyPort = controlPort;
  476.  
  477.    InitBlock(iob,0);   /* init it for CMD_WRITE, then change */
  478.  
  479.    iob->ioa_Request.io_Message.mn_Node.ln_Pri = 20;
  480.    iob->ioa_Request.io_Command = ADCMD_ALLOCATE;
  481.  
  482.    if(ch == -1)
  483.    {   
  484.     iob->ioa_Data = (UBYTE *)anychan;
  485.           iob->ioa_Length = 4;
  486.    }
  487.    else if(ch >=0 && ch <= 3)
  488.    {       
  489.     /* Test to be sure that this channel is now free.  If
  490.      * usertask[i] is not zero, either the current task 
  491.      * has already allocated this channel, or a different
  492.      * task is now using it. 
  493.      */
  494.  
  495.     /* NOTE ***** ENHANCEMENT COMING HERE ***** */
  496.  
  497.     if(usertask[ch] != 0) return(-1);
  498.  
  499.     /* Enhancement might be: look at the running priority
  500.       * of the current task as compared to the running priority
  501.      * of the task in usertask[i].  If not same task and if
  502.      * the current task has a higher priority, STEAL the channel!
  503.      * Alternative (seems better) is to have a global variable
  504.      * called audPriority to be set by a new function SetAudPriority
  505.      * (for a given task only), and that global priority value
  506.      * would be used for GetChannel and LockChannel requests.
  507.      */
  508.     iob->ioa_Data = (UBYTE *)(&anychan[ch]);
  509.           iob->ioa_Length = 1;
  510.    }
  511.    else   /* chose a bad channel number; cannot allocate it */
  512.    {   
  513.     return(-1);
  514.    }
  515.    iob->ioa_Request.io_Flags = ADIOF_NOWAIT | IOF_QUICK;
  516.    BeginIO(iob); 
  517.    error = WaitIO(iob);  /* returns nonzero if error */
  518.    if(!(iob->ioa_Request.io_Flags & IOF_QUICK))
  519.    {   
  520.     GetMsg(iob->ioa_Request.io_Message.mn_ReplyPort);
  521.    }
  522.    if(error)
  523.    {   
  524.     return(-1);
  525.    }
  526.    switch((LONG)(iob->ioa_Request.io_Unit))
  527.    {   
  528.     case  1:   value = 0;   break;
  529.           case  2:   value = 1;   break;
  530.           case  4:   value = 2;   break;
  531.           case  8:   value = 3;   break;
  532.           default:   value = -1;  break;
  533.    }
  534.    if(value == -1) return(-1L);
  535.  
  536.    unit[value]     = (iob->ioa_Request.io_Unit);
  537.    key[value]      = (iob->ioa_AllocKey);
  538.    usertask[value] = FindTask(0);    /* THIS user task owns it now */
  539.  
  540.    return(value);
  541. }
  542.  
  543. int
  544. FreeChannel(channel)
  545.    LONG channel;
  546. {
  547.    int error;
  548.    struct ExtIOB *iob, controlIOB;
  549.  
  550.    if(channel < 0 || channel > 3)    return(BAD_CHANNEL_SELECTED);
  551.    if(usertask[channel] != FindTask(0))    return(NOT_YOUR_CHANNEL);
  552.  
  553.    iob = &controlIOB;
  554.    iob->ioa_Request.io_Device    = device;
  555.    iob->ioa_Request.io_Message.mn_ReplyPort = controlPort;
  556.  
  557.    InitBlock(iob,channel);       /* init it for CMD_WRITE, then change it */
  558.                     /* (pick up unit, key value for channel) */
  559.    iob->ioa_Request.io_Command = ADCMD_FREE;
  560.    iob->ioa_Request.io_Flags = ADIOF_NOWAIT | IOF_QUICK;
  561.    BeginIO(iob); 
  562.    error = WaitIO(iob);  /* returns nonzero if error */
  563.  
  564.    /* Educational note - the docs indicate that audio, even though 
  565.     * told to do an io item as "quickio", can, if it chooses, not do
  566.     * it quick.  In that case, the reply is queued to the
  567.     * reply port and it has to be removed.  That is why
  568.     * the following status check has been installed here.
  569.     * (Actually this check probably can be removed because
  570.     * WaitIO, just above, removes the message from the port
  571.     * if it does arrive there after all.)
  572.     */
  573.    if(!(iob->ioa_Request.io_Flags & IOF_QUICK))
  574.    {       
  575.     GetMsg(iob->ioa_Request.io_Message.mn_ReplyPort);
  576.    }
  577.    usertask[channel] = 0;    /* free again... */
  578.    if(error)
  579.    {   
  580.     return(error);
  581.    }
  582.    return(0);
  583. }
  584.  
  585. /* SOME OF THE FOLLOWING ROUTINES ARE PARAPHRASED FROM A USENET and BIX
  586.  * POSTING MADE IN 1985 BY STEVEN A. BENNETT.  
  587.  */
  588. /* I have modified his routines to queue the audio commands in 
  589.  * place of starting forever-duration and canceling each note.
  590.  * Many of his original comments have been incorporated into
  591.  * the article. 
  592.  */
  593.  
  594. /* PlayNote(...) */
  595.  
  596. /* ******************************************************************* */
  597. /* NOTE: There are some differences in PlayNote as compared to the article.
  598.  * See the audiotools.DOC for details.
  599.  */
  600. /* ******************************************************************* */
  601.  
  602. /* Starts a sound on the channel with specified period and volume. */
  603. /* This nice little routine takes a note and plays it on the given
  604.  * voice.  The note is basically an integer from
  605.  * 0 to 11 (c to b) plus 12 per octave above the first and lowest. 
  606.  *
  607.  * The waveform to use is determined by adding an index (woffsets[]) 
  608.  * dependant on the octave.
  609.  *
  610.  * The length of the waveform (in wlen[]) is likewise dependant on
  611.  * the octave.  Note that octaves start with zero, not one.
  612.  */
  613. int 
  614. PlayNote(channel, note, wf, vol, duration, priority, messageport, id)
  615.    char *wf;   /* waveform to use */
  616.    LONG vol, channel, duration, note;   /* specific note number */
  617.    LONG priority;
  618.    struct MsgPort *messageport;
  619.    LONG id;
  620. {
  621.    LONG per, len, oct;   /* period, length of waveform, which octave */
  622.    char *wavepointer;   /* where to find start of waveform */
  623.    struct ExtIOB *iob;
  624.    int frequency;
  625.  
  626.    if(channel < 0 || channel > 3)    return(BAD_CHANNEL_SELECTED);
  627.    if(usertask[channel] != FindTask(0))    return(NOT_YOUR_CHANNEL);
  628.    if(note < 0 || note > 95)         return(OUT_OF_RANGE_FREQ);
  629.    if(priority < -128 || priority > 127)return(OUT_OF_RANGE_PRI);
  630.  
  631.    iob = GetIOB(channel);
  632.  
  633.    if(iob != 0)
  634.    {
  635.     InitBlock(iob, channel);   /* set up for CMD_WRITE */
  636.    
  637.     oct = note / 12;
  638.        wavepointer = wf + woffsets[oct];
  639.        len = wlen[oct];
  640.        per = perval[note % 12];
  641.  
  642.        /* Set the parameters */
  643.        iob->ioa_Data = (UBYTE *)wavepointer;
  644.        iob->ioa_Length = len;
  645.        iob->ioa_Period = per;
  646.        iob->ioa_Volume = vol;
  647.  
  648.     iob->ioa_Request.io_Message.mn_Node.ln_Pri = priority;
  649.  
  650.        /* additions for support of tell-me-when-note-starts */
  651.  
  652.           iob->iob_Identifier = id;
  653.  
  654.     /* Initialize message port.  If 0, then no pushing back
  655.      * of a message.  If nonzero, message gets recirculated
  656.      * by ReEmployIOB until the user finally acknowledges it
  657.      * by using MayGetNote.
  658.       */
  659.     iob->ioa_WriteMsg.mn_ReplyPort = messageport;
  660.  
  661.     if(messageport != 0)
  662.        {
  663.               /* If 0, no sending message when note plays;
  664.                 * if nonzero, user gets a message that can
  665.              * be read by MayGetNote.  uport, received from 
  666.          * InitAudio, is where the message goes */
  667.  
  668.         /* Tell the audio device to "reply" to this message */
  669.         iob->ioa_Request.io_Flags |= ADIOF_WRITEMESSAGE;
  670.         }
  671.  
  672.    /* Look at the frequency that it is to play by backwards calc. */
  673.    frequency = 3579545 / (len * per);
  674.  
  675.    /* Calculate cycles from duration in 1000ths of a second */
  676.    /* Multiply all-in-one to maintain max precision possible */
  677.    /* (all integer arithmetic.) */
  678.  
  679.    iob->ioa_Cycles = ((LONG)(frequency * duration)/1000);
  680.    BeginIO(iob);
  681.    return(0);      /* all went ok */
  682.    }
  683. return(-1);    /* (else-part) iob was zero, couldn't do the above. */
  684. }
  685.  
  686.   
  687. /* SetPV(channel, per, vol)
  688.  *   int channel, per, vol;
  689.  */
  690. int 
  691. SetPV(channel, per, vol)
  692.    int channel, per, vol;
  693.    {
  694.    int error;
  695.    struct ExtIOB *iob, controlIOB;
  696.  
  697.    if(channel < 0 || channel > 3)    return(BAD_CHANNEL_SELECTED);
  698.    if(usertask[channel] != FindTask(0))    return(NOT_YOUR_CHANNEL);
  699.  
  700.    iob = &controlIOB;
  701.    iob->ioa_Request.io_Device    = device;
  702.    iob->ioa_Request.io_Message.mn_ReplyPort = controlPort;
  703.  
  704.    InitBlock(iob, channel);   /* set up for CMD_WRITE */
  705.    
  706.    iob->ioa_Period = per;
  707.    iob->ioa_Volume = vol;
  708.    iob->ioa_Request.io_Command = ADCMD_PERVOL;
  709.    iob->ioa_Request.io_Flags   = IOF_QUICK | ADIOF_PERVOL;
  710.    BeginIO(iob);   /* This one will be synchronous; affects whatever
  711.           * is playing on this channel at this time.
  712.           */
  713.    error = WaitIO(iob);   /* OK to wait, since it will return */
  714.    return(error);      /* copy of io_Error field; should be 0 */
  715. }
  716.  
  717. /* SetWaves(w1, w2, w3): create first sawtooth, triangle and square wave */
  718.  
  719. SetWaves(w1, w2, w3)
  720.    UBYTE *w1, *w2, *w3;
  721. {
  722.    int i, increment, value, sqvalue;
  723.    value = 0; increment = 2;
  724.    sqvalue = 127;
  725.  
  726.    for (i = 0; i < BIG_WAVE; ++i)
  727.    {
  728.    w1[i] = i;   /* do the sawtooth */
  729.  
  730.    if(i > 62 && i < 180) increment = -2;
  731.    else
  732.    if(i >= 180) increment = 2;
  733.  
  734.    w2[i] = value;  value += increment;  /* triangle wave */
  735.  
  736.    if(i > 126) sqvalue = -127;
  737.  
  738.    w3[i] = sqvalue;
  739.    }
  740. return(0);
  741. }
  742.   
  743. /* ExpandWave(wfp) - replicate waves in decreasing sample sizes
  744.  *   BYTE *wfp;
  745.  */
  746.  
  747. ExpandWave(wfp)
  748.    BYTE *wfp;
  749.    {
  750.    int i, j, rate;
  751.    BYTE *tptr;
  752.  
  753.    rate = 1;
  754.    tptr = wfp + BIG_WAVE;
  755.    for (i = 0; i < NBR_WAVES - 1; ++i)
  756.       {
  757.       rate *= 2;
  758.       for (j = 0; j < BIG_WAVE; j += rate)
  759.          *tptr++ = wfp[j];
  760.       }
  761.    return(0);
  762.    }
  763.   
  764. /* MakeWaves()
  765.  *
  766.  *   Just makes a sawtooth, triangle and square wave in chip mem 
  767.  * and expands them.
  768.  */
  769. int 
  770. MakeWaves()
  771. {
  772.    /* allocate the memory for the waveforms.
  773.     */
  774.    w1 = (UBYTE *)AllocMem(WAVES_TOTAL, MEMF_CHIP);
  775.    w2 = (UBYTE *)AllocMem(WAVES_TOTAL, MEMF_CHIP);
  776.    w3 = (UBYTE *)AllocMem(WAVES_TOTAL, MEMF_CHIP);
  777.  
  778.    if (w1 == NULL || w2 == NULL || w3 == NULL)
  779.    return(-1);   /* ran out of memory! */
  780.  
  781.    /* get and expand the waveforms    */
  782.  
  783.    SetWaves(w1, w2, w3);
  784.    ExpandWave(w1);   chipaudio[0]=w1;
  785.    ExpandWave(w2);   chipaudio[1]=w2;
  786.    ExpandWave(w3);   chipaudio[2]=w3;
  787.    return(0);
  788. }
  789.  
  790. /* =================================================================== */
  791. /* Here are NEW routines that did not appear in the AmigaWorld article.
  792.  * These are in addition to the changes noted above for InitAudio and
  793.  * FinishAudio, and changes in main() to accommodate them.
  794.  */
  795.  
  796. /* PlayFreq in this version is for INTEGER values of frequency only.
  797.  *
  798.  * To be more precise, you'll have to convert the integer calculations
  799.  * to floating point.   Almost identical to PlayNote, however, had to
  800.  * add some error checking to the parameters AHEAD OF GetIOB because
  801.  * if the frequency is out of range of what we have in our wave tables
  802.  * currently, we have to reject the command.  If it got rejected
  803.  * after Getting an IOB, then we'd have to free it.
  804.  */
  805. int 
  806. PlayFreq(channel, freq, wf, vol, duration, priority, messageport, id)
  807.    char *wf;   /* waveform to use */
  808.    LONG vol, channel, duration, freq;   /* specific integer frequency */
  809.    LONG priority;
  810.    struct MsgPort *messageport;
  811.    LONG id;
  812. {
  813.    LONG per, len, oct;   /* period, length of waveform, which octave */
  814.    char *wavepointer;   /* where to find start of waveform */
  815.    struct ExtIOB *iob;
  816.    LONG i, isave, accept;
  817.  
  818.    if(channel < 0 || channel > 3)    return(BAD_CHANNEL_SELECTED);
  819.    if(usertask[channel] != FindTask(0)) return(NOT_YOUR_CHANNEL);
  820.    if(priority < -128 || priority > 127)return(OUT_OF_RANGE_PRI);
  821.  
  822.    /* see if we CAN represent this frequency, if not, reject it */
  823.  
  824.    for(i=0; i<9; i++)
  825.    {
  826.        /* Now figure out which waveform to use...
  827.             * start with the first wlen value because
  828.             * we want to use the longest waveform we can.
  829.             */
  830.        isave = i;   
  831.  
  832.        accept = FALSE;
  833.  
  834.        per = 3579545 / (freq * wlen[i]);
  835.  
  836.        if(per > 127 && per < 500)
  837.        {
  838.               accept = TRUE;
  839.               break;
  840.        }
  841.    }
  842.    if(accept != TRUE)             return(OUT_OF_RANGE_FREQ);
  843.  
  844.    iob = GetIOB(channel);
  845.  
  846.    if(iob != 0)
  847.    {
  848.        InitBlock(iob, channel);   /* set up for CMD_WRITE */
  849.  
  850.        oct = isave;         /* show the octave we are in */
  851.        wavepointer = wf + woffsets[oct];   /* point to longest wave */
  852.        len = wlen[oct];      /* show its length */
  853.  
  854.           /* Set the parameters */
  855.           iob->ioa_Data = (UBYTE *)wavepointer;
  856.           iob->ioa_Length = len;
  857.           iob->ioa_Period = per;
  858.           iob->ioa_Volume = vol;
  859.  
  860.     iob->ioa_Request.io_Message.mn_Node.ln_Pri = priority;
  861.  
  862.        /* additions for support of tell-me-when-note-starts */
  863.           iob->iob_Identifier = id;
  864.  
  865.     /* see note on same line for PlayNote above */
  866.     iob->ioa_WriteMsg.mn_ReplyPort = messageport;
  867.  
  868.     if(messageport != 0)
  869.        {
  870.               /* If 0, no sending message when note plays;
  871.                 * if nonzero, user gets a message that can
  872.              * be read by MayGetNote.  uport, received from 
  873.          * InitAudio, is what should be the value of
  874.          * messageport. 
  875.          */ 
  876.  
  877.         /* Tell the audio device to "reply" to this message */
  878.         iob->ioa_Request.io_Flags |= ADIOF_WRITEMESSAGE;
  879.         }
  880.  
  881.    /* Calculate cycles from duration in 1000ths of a second */
  882.    /* Multiply all-in-one to maintain max precision possible */
  883.    /* (all integer arithmetic.) */
  884.  
  885.    iob->ioa_Cycles = ((LONG)(freq * duration)/1000);
  886.    BeginIO(iob);
  887.    return(0);      /* all went ok */
  888.    }
  889. return(-1L);    /* (else-part) iob value was zero, out of memory probably */
  890. }
  891.  
  892.  
  893. /* Another new routine called MayGetNote.  Its syntax is:
  894.  *
  895.  *      note = MayGetNote(uport, flag)
  896.  *
  897.  * where uport is the address of the port you received from InitAudio.
  898.  *
  899.  *       flag  is a value of 0 or 1.
  900.  *
  901.  *          when flag = 0, the routine returns immediately, with
  902.  *               a note value of 0 (no note available), or
  903.  *               the value of the first note notification 
  904.  *               to arrive at the port.
  905.  *
  906.  *          when flag = nonzero, the routine will wait if (and only if)
  907.  *               there is no note to be had.  In other words,
  908.  *               you can cause your task to go to sleep until
  909.  *               the next note begins to play.  You decide
  910.  *               what to do for a specific note value.
  911.  *
  912.  *     note  is the value you placed into the PlayNote or PlayFreq
  913.  *               identifier field.  A report of this type
  914.  *               is ONLY generated when "messageport" is nonzero. 
  915.  *   
  916.  *   CAUTION - if there are no more notes with messageport nonzero in
  917.  *         the queue and you specify TRUE for the flag, you can cause your
  918.  *         task to sleep forever!!
  919.  */
  920.  
  921. LONG
  922. MayGetNote(uport, flag)
  923.    struct MsgPort *uport;
  924.    LONG flag;
  925. {
  926.    struct auMsg *aum;
  927.    LONG wakemask;
  928.    struct Message *writemsg;
  929.   
  930. grabmessage:
  931.    aum = (struct auMsg *)GetMsg(uport);   /* is a message there? */
  932.  
  933.    if(aum)   /* There was a message! */ 
  934.    {
  935.       /* The user has seen this msg, so the system can deallocate
  936.        * the iob in which it occurs anytime in the future.
  937.        * Now that we have received the message at our own reply
  938.        * port, it belongs to us and we can do whatever we want
  939.        * to it.  Set the reply port value to zero now, as a signal
  940.        * to FreeIOB that it can really do that! 
  941.        */
  942.       aum->au_Message.mn_ReplyPort = 0;
  943.  
  944.       return(aum->aum_Identifier);  /* return the value */
  945.    }
  946.    if(flag) /* If nonzero, we cause caller to sleep while
  947.              * waiting for any identified iob to appear.
  948.              */
  949.    {
  950.       wakemask = WaitPort(uport);  /* not using wakemask */
  951.       flag = 0;
  952.       goto grabmessage;   /* a goto!  aaarrrggghhh! */
  953.    }
  954. return(0L);   
  955. }
  956.  
  957. /* ReEmployIOB is not new (as compared to article) but it
  958.  * has changed a lot since the article was written.
  959.  */
  960.  
  961. /* ReEmployIOB - look at ALL of the reply ports and if any IOBs
  962.  *        hanging around with nothing to do, free them.
  963.  *
  964.  *       In the article, this routine removed iob's from the
  965.  *       reply ports via the usual GetMsg function.  This
  966.  *       was not practical in this case when adding the support
  967.  *       for audio messages from the device.  
  968.  *
  969.  *  Problem was this:
  970.  *       Audio may still be playing the waveform as we get a
  971.  *       message through MayGetNote.  MayGetNote marks the
  972.  *       iob message block as free-able, (when it finds that
  973.  *       the identifier field is set to zero) but we have to have a
  974.  *       way of recirculating in this list of messages.
  975.  *
  976.  *       In other words, if something is free-able, free it,
  977.  *       otherwise leave it on the list.  So rather than
  978.  *       removing things from the front of the list, lets just
  979.  *       walk through the message list, Remove (dequeue) what is 
  980.  *       freeable and leave the rest there to look at the next time.
  981.  */
  982.  
  983. ReEmployIOB()
  984. {
  985.    LONG i;
  986.    struct MsgPort *mp;
  987.    struct ExtIOB *iob;
  988.  
  989.    /* now declare a pointer to "the first iob pushed back onto the port" */
  990.  
  991.    struct ExtIOB *pushback;    
  992.  
  993.    /* What happens here is that iob's are removed from the message port
  994.     * when they come back from the audio device.   If YOU have set the
  995.     * messageport nonzero, it means that you wanted to know when
  996.     * this note began to play.  The WriteMsg part of the iob is then
  997.     * linked, as a message, onto your user port.  So this routine here
  998.     * cannot free the iob until it is certain that YOU have finished
  999.     * using it.  The iob_Priority field is READ here.  If it still
  1000.     * nonzero, the iob is pushed back onto the message port (on the
  1001.     * end of the message queue) to be read again.  We hold a pointer
  1002.     * named pushback that lets us keep track of when we see that
  1003.     * again.  If we see it twice, it means we have completed a full
  1004.     * circle through the queue of messages and have freed everything
  1005.     * that we can this time.  Therefore, we examine it and either
  1006.     * free it or push it back again, then exit.
  1007.     */
  1008.  
  1009.    /* Tiny drawback to the pushback that we do is that there can
  1010.     * be more than one dynamic IOB with the same identifier value.
  1011.     * The id value is not used anywhere, execept for the debug
  1012.     * reporting, so it is not critical.  Note that this is not
  1013.     * a problem with a pushback of a static iob because the inuse[]
  1014.     * array is being maintained for the statics.
  1015.     */
  1016.    for(i=0; i<4; i++)   /* remove all iob's from ALL ports */
  1017.             /* (that is, unless we have to push one back) */
  1018.    {   
  1019.       mp = replyPort[i];
  1020.  
  1021.       pushback = (struct ExtIOB *)0;    /* nothing pushed back so far */
  1022.  
  1023.       while((iob = (struct ExtIOB *)GetMsg(mp)) != 0)
  1024.       { 
  1025.         /* Here is what triggers the Identifier message for MayGetNote */
  1026.  
  1027.         /* First see if messageport in WriteMsg is greater than zero;
  1028.          * if so, audio device is done, but user hasnt acknowledged
  1029.          * this message yet (by using MayGetNote).
  1030.          */
  1031.         if(iob->ioa_WriteMsg.mn_ReplyPort != 0)
  1032.         {
  1033.         /* If we get here, we already know we will push it back */
  1034.         /* because User has not yet freed it (using MayGetNote) */
  1035.  
  1036.         PutMsg(mp, iob);
  1037.  
  1038.         if(iob == pushback) /* If so, we saw it already */
  1039.         {
  1040.             break;       /* Go out to empty next port */
  1041.         }
  1042.         if(pushback == 0)
  1043.         {
  1044.             pushback = iob;    /* Remember FIRST one pushed back */
  1045.         }
  1046.         }
  1047.         else    /* messageport value is zero, can free the iob */
  1048.         {
  1049. #ifdef DEBUG
  1050.             printf("freeing %ls iob; ID=%ld\n",
  1051.                   iob->ioa_Request.io_Message.mn_Node.ln_Name,
  1052.                         iob->ioa_Request.io_Message.mn_Length);
  1053. #endif DEBUG
  1054.                 FreeIOB(iob,i);
  1055.         }
  1056.       }
  1057.  
  1058.    }
  1059.    return(0);
  1060. }
  1061.  
  1062. /* Use this next function to determine if you (still) own a particular channel.
  1063.  * The audio device has an arrangement by which a higher priority request
  1064.  * for a channel than the one that already owns it can be made.  The higher
  1065.  * priority request can actually cause a channel to be stolen from a user.
  1066.  * This feature may be implemented in a future version of audiotools,
  1067.  * (shared library version), in which, depending on the task's running
  1068.  * priority itself, a higher priority task could succeed at GetChannel
  1069.  * for a channel that is already owned by another task.
  1070.  */
  1071.  
  1072. int
  1073. IsThatMyChan(channel)    /* returns nonzero if YOU still own the channel */
  1074.     LONG channel;
  1075. {
  1076.     if(channel < 0 || channel > 3)         return(0);
  1077.     if(usertask[channel] == FindTask(0))     return(TRUE);
  1078.     return(0);
  1079. }
  1080.  
  1081.  
  1082.